﻿// Copyright © Microsoft Corporation.  All Rights Reserved.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;

namespace Oliver.Controls.Behaviors
{
    public class FlagBehavior : Behavior<UIElement>
    {
        public FlagBehavior()
        {

        }

        protected override void OnAttached()
        {
            baseImage = this.AssociatedObject as Image;

            base.OnAttached();
        }

        //Panel _centeredGrid;
        //public Panel CenteredGrid
        //{
        //    get { return _centeredGrid; }
        //    set { _centeredGrid = value; }
        //}

        Image baseImage;

        const int SIDE = 20;
        const int TOP_WAVELENGTH = 251;
        const int BOT_WAVELENGTH = 433;
        const int TOP_AMP = 10;
        const int BOT_AMP = 15;
        const int MAX_PHASE = TOP_WAVELENGTH * BOT_WAVELENGTH;
        Path[,] paths;
        Point[,] coordinates;

        public static readonly DependencyProperty PhaseProperty =
            DependencyProperty.Register("Phase",
                typeof(double),
                typeof(Page),
                new PropertyMetadata(0.0, OnPhaseChanged));

        public double Phase
        {
            set { SetValue(PhaseProperty, value); }
            get { return (double)GetValue(PhaseProperty); }
        }

        void OnImageFailed(object sender, ExceptionRoutedEventArgs args)
        {
            MessageBox.Show(args.ErrorException.Message, "Open Image Failed", MessageBoxButton.OK);
        }

        void OnImageOpened(object sender, RoutedEventArgs args)
        {
            PrepareImages(baseImage, /*_centeredGrid, */ SIDE);
            baseImage.Visibility = Visibility.Collapsed;

            DoubleAnimation anima = new DoubleAnimation()
            {
                From = 0,
                To = MAX_PHASE,
                Duration = new Duration(TimeSpan.FromMinutes(20)),
                RepeatBehavior = RepeatBehavior.Forever
            };

            Storyboard storyboard = new Storyboard();
            storyboard.Children.Add(anima);
            Storyboard.SetTarget(anima, this);
            Storyboard.SetTargetProperty(anima, new PropertyPath(FlagBehavior.PhaseProperty));
            storyboard.Begin();
        }

        static void OnPhaseChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as FlagBehavior).OnPhaseChanged(args);
        }

        void OnPhaseChanged(DependencyPropertyChangedEventArgs args)
        {
            TransformImages(SIDE, (int)Phase);
        }

        void PrepareImages(FrameworkElement sourceElement,
            //Panel destinationPanel, 
            int side)
        {
            Canvas destinationPanel = new Canvas();
            //destinationPanel.Children.Clear();

            int numHorz = (int)(sourceElement.ActualWidth + side - 1) / side;
            int numVert = (int)(sourceElement.ActualHeight + side - 1) / side;

            paths = new Path[numHorz, numVert];
            coordinates = new Point[numHorz + 1, numVert + 1];

            for (int y = 0; y < sourceElement.ActualHeight; y += side)
            {
                for (int x = 0; x < sourceElement.ActualWidth; x += side)
                {
                    WriteableBitmap bitmap = new WriteableBitmap(side + 1, side + 1);
                    TranslateTransform translate = new TranslateTransform()
                    {
                        X = -x,
                        Y = -y
                    };
                    bitmap.Render(sourceElement, translate);
                    bitmap.Invalidate();

                    RectangleGeometry rectGeometry = new RectangleGeometry()
                    {
                        Rect = new Rect(x, y, side + 1, side + 1)
                    };

                    ImageBrush imgBrush = new ImageBrush()
                    {
                        ImageSource = bitmap
                    };

                    paths[x / side, y / side] = new Path()
                    {
                        Data = rectGeometry,
                        Fill = imgBrush,
                        Projection = new Matrix3DProjection()
                    };

                    destinationPanel.Children.Add(paths[x / side, y / side]);
                    //baseImage
                }
            }

            WriteableBitmap bmp = new WriteableBitmap(destinationPanel, new ScaleTransform());
            baseImage.Source = bmp;
            TransformImages(side, 0);
        }

        void TransformImages(int side, int phase)
        {
            int numHorz = paths.GetLength(0);
            int numVert = paths.GetLength(1);

            for (int j = 0; j <= numVert; j++)
                for (int i = 0; i <= numHorz; i++)
                {
                    int x = i * side;

                    int topAmp = Sine(x, -phase, TOP_WAVELENGTH, TOP_AMP);
                    int botAmp = Sine(x, phase, BOT_WAVELENGTH, BOT_AMP);
                    int amp = (j * botAmp + (numVert - j) * topAmp) / numVert;

                    int y = j * side + amp;
                    x += amp;

                    coordinates[i, j] = new Point(x, y);
                }

            for (int j = 0; j < numVert; j++)
                for (int i = 0; i < numHorz; i++)
                {
                    RectangleGeometry rectGeometry = paths[i, j].Data as RectangleGeometry;
                    Matrix3DProjection projection = paths[i, j].Projection as Matrix3DProjection;

                    Rect rect = new Rect(rectGeometry.Rect.X, rectGeometry.Rect.Y, side, side);

                    Matrix3D matx = CalculateNewTransform(rect,
                                                         coordinates[i, j], coordinates[i + 1, j],
                                                         coordinates[i, j + 1], coordinates[i + 1, j + 1]);

                    projection.ProjectionMatrix = matx;
                }
        }

        // All arguments in pixels
        int Sine(int xPixels, int phase, int wavelength, int amplitude)
        {
            return (int)(amplitude * Math.Sin(2 * Math.PI * (xPixels + phase) / wavelength));
        }

        Matrix3D CalculateNewTransform(Rect rect, Point ptUL, Point ptUR, Point ptLL, Point ptLR)
        {
            // Scale and translate transform
            Matrix3D S = new Matrix3D()
            {
                M11 = 1 / rect.Width,
                M22 = 1 / rect.Height,
                OffsetX = -rect.Left / rect.Width,
                OffsetY = -rect.Top / rect.Height
            };

            // See http://www.charlespetzold.com/blog/2007/08/250638.html for math that follows

            // Affine transform
            Matrix3D A = new Matrix3D()
            {
                OffsetX = ptUL.X,
                OffsetY = ptUL.Y,
                M11 = (ptUR.X - ptUL.X),
                M12 = (ptUR.Y - ptUL.Y),
                M21 = (ptLL.X - ptUL.X),
                M22 = (ptLL.Y - ptUL.Y)
            };

            // Non-Affine transform
            Matrix3D B = new Matrix3D();
            double den = A.M11 * A.M22 - A.M12 * A.M21;
            double a = (A.M22 * ptLR.X - A.M21 * ptLR.Y + A.M21 * A.OffsetY - A.M22 * A.OffsetX) / den;
            double b = (A.M11 * ptLR.Y - A.M12 * ptLR.X + A.M12 * A.OffsetX - A.M11 * A.OffsetY) / den;

            B.M11 = a / (a + b - 1);
            B.M22 = b / (a + b - 1);
            B.M14 = B.M11 - 1;
            B.M24 = B.M22 - 1;

            // Product of three transforms
            return S * B * A;
        }
    }
}
